﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;

namespace WinHooksNET
{
    public class HotKeysModule
    {
        #region Members
        public event EventHandler<Events.HotKeyEventArgs> OnHotKeyAction;

        private List<Structs.HotKey> _hotKeys = new List<Structs.HotKey>();
        private List<Key> _clickedKeys = new List<Key>();
        private List<Events.HotKeyEventArgs> _eventsHistory = new List<Events.HotKeyEventArgs>();

        private KeyboardHook _keyboardHook = null;

        private bool _handleHotKeyPress = true;
        private bool _handleHotKeyRelease = true;

        private Enums.FilterMode _filterMode = Enums.FilterMode.BooleanFilters;

        private int _historyLimit = 100;
        
        private Func<bool> _customFilterFunc;

        /// <summary>
        /// Determine whether the library is to intercept key release
        /// </summary>
        public bool HandleHotKeyPress
        {
            get { return _handleHotKeyPress; }
            set { _handleHotKeyPress = value; }
        }

        /// <summary>
        /// Determine whether the library is to intercept key release
        /// </summary>
        public bool HandleHotKeyRelease
        {
            get { return _handleHotKeyRelease; }
            set { _handleHotKeyRelease = value; }
        }

        /// <summary>
        /// List of called events. Limit of items can be set in HistoryLimit var
        /// </summary>
        public List<Events.HotKeyEventArgs> EventsHistory
        {
            get { return _eventsHistory; }
            set { _eventsHistory = value; }
        }

        /// <summary>
        /// Limit of items in called event history. If 0, history can be empty
        /// </summary>
        public int HistoryLimit
        {
            get { return _historyLimit; }
            set
            {
                _historyLimit = value;
                if (EventsHistory.Count >= HistoryLimit)
                {
                    EventsHistory.RemoveRange(HistoryLimit, EventsHistory.Count - HistoryLimit);
                }
            }
        }

        /// <summary>
        /// Determine Filter Mode. BooleanFilter for bool variables or CustomFunction for CustomFilterFunc var.
        /// </summary>
        public Enums.FilterMode FilterMode
        {
            get { return _filterMode; }
            set { _filterMode = value; }
        }

        /// <summary>
        /// Custom function used by filter. FilterMode must be set to a CustomFunction
        /// </summary>
        public Func<bool> CustomFilterFunc
        {
            get { return _customFilterFunc; }
            set { _customFilterFunc = value; }
        }
        #endregion

        public HotKeysModule()
        {
            //Create new keyboard hook
            _keyboardHook = new KeyboardHook();
            _keyboardHook.OnKeyboardAction += OnKeyboardAction;

            _customFilterFunc = new Func<bool>(() => { return true; });
        }

        #region Functions
        /// <summary>
        /// Register a new hot key with a specific name
        /// </summary>
        /// <param name="name">Specific name to hot key</param>
        /// <param name="keys">List of keys</param>
        public void RegisterHotKey(String name, params Key[] keys)
        {
            //If name of hot key exist in list of hot keys, throw exception
            if(_hotKeys.Exists(p => p.Name == name))
                throw new Exceptions.NameExistException("HotKey name is already exist");

            //Init new hot key
            Structs.HotKey hotKey = new Structs.HotKey();
            hotKey.Name = name;
            hotKey.Keys = keys.ToList();

            //Add hot key to list
            _hotKeys.Add(hotKey);
        }

        /// <summary>
        /// Unregister hot key with a specific name
        /// </summary>
        /// <param name="name">Name of hot key</param>
        public void UnregisterHotKey(String name)
        {
            //If any hot key exist
            if(_hotKeys.Exists(p => p.Name == name))
            {
                //Remove all hot keys with a specific name
                _hotKeys.RemoveAll(p => p.Name == name);
            }
        }

        private void OnKeyboardAction(object sender, Events.KeyboardHookEventArgs e)
        {
            //Add or remove key from clicked keys list
            if (e.State == Enums.KeyState.Pressed)
            {
                _clickedKeys.Add(e.Key);
            }
            else if (e.State == Enums.KeyState.Released)
            {
                _clickedKeys.RemoveAll(p => p == e.Key);
            }

            if (IsActionAllowed(e.State))
            {
                //Init list of called hot keys
                List<Structs.HotKey> checkedHotKeys = CheckHotKeys().ToList();

                for (int i = 0; i < checkedHotKeys.Count(); i++)
                {
                    Events.HotKeyEventArgs eventArgs =  new Events.HotKeyEventArgs()
                    {
                        Name = checkedHotKeys[i].Name,
                        Keys = checkedHotKeys[i].Keys,
                        Time = DateTime.Now.Ticks
                    };

                    AddEventToHistory(eventArgs);

                    //Call OnHotKeyAction event with specific name and list of keys
                    OnHotKeyAction(this, eventArgs);
                }
            }
        }

        private IEnumerable<Structs.HotKey> CheckHotKeys()
        {
            if (_clickedKeys.Count() > 0)
            {
                //List of hot keys
                for (int x = 0; x < _hotKeys.Count; x++)
                {
                    //Init isHotKey flag
                    bool isHotKey = true;

                    //If clicked keys count is smaller than keys in hot key, skip this
                    if (_hotKeys[x].Keys.Count > _clickedKeys.Count)
                    {
                        continue;
                    }

                    //List of clicked keys
                    for (int y = 0; y < _clickedKeys.Count; y++)
                    {
                        //If clicked key isn't in list of keys in hot key, skip this
                        if (_hotKeys[x].Keys[y] != _clickedKeys[y])
                        {
                            isHotKey = false;
                            break;
                        }
                    }

                    //If processed hot key is called
                    if (isHotKey)
                    {
                        //Add new hot key to IEnumerable list
                        yield return new Structs.HotKey()
                        {
                            Name = _hotKeys[x].Name,
                            Keys = _hotKeys[x].Keys,
                        };
                    }
                }
            }
        }

        private bool IsActionAllowed(Enums.KeyState action)
        {
            if (_filterMode == Enums.FilterMode.BooleanFilters)
            {
                if (((action == Enums.KeyState.Pressed) && HandleHotKeyPress) ||
                    ((action == Enums.KeyState.Released) && HandleHotKeyRelease))
                {
                    return true;
                }
            }
            else if (_filterMode == Enums.FilterMode.CustomFunction)
            {
                bool result = _customFilterFunc();
                return result;
            }

            return false;
        }

        private void AddEventToHistory(Events.HotKeyEventArgs _eventArgs)
        {
            if (HistoryLimit != 0)
            {
                if (EventsHistory.Count >= HistoryLimit)
                {
                    if (EventsHistory.Count > 0)
                    {
                        EventsHistory.RemoveAt(EventsHistory.Count - 1);
                    }
                }
                EventsHistory.Insert(0, _eventArgs);
            }
        }
        #endregion
    }
}
